using UnityEngine;
using System;
using System.IO;
using System.Collections.Generic;
using System.Net;
using Lidgren.Network;
using System.Runtime.Serialization.Formatters.Binary;

public class AutoPeer : NetPeer
{

	#region private

	private Dictionary<NetIncomingMessageType, MessageHandler> m_messageHandlers;
	private Dictionary<Type, DataHandler> m_dataHandlers;

	/// <summary>
	/// Dispatches data to subscribed handlers
	/// </summary>
	protected void DataDispatcher(NetIncomingMessage message, AutoPeer instance)
	{
		object data = UnpackObject(message);

		DataHandler handler;
		if (m_dataHandlers.TryGetValue(data.GetType(), out handler))
		{
			handler(message, data, this);
		}
		else if (defaultDataHandler != null)
		{
			defaultDataHandler(message, data, instance);
		}
	}

	#endregion

	#region protected

	/// <summary>
	/// Packs object into outgoing message using .NET serialization
	/// </summary>
	protected NetOutgoingMessage PackObject(object obj)
	{
		MemoryStream stream = new MemoryStream();
		BinaryFormatter formatter = new BinaryFormatter();
		formatter.Serialize(stream, obj);
		byte[] buffer = stream.ToArray();

		NetOutgoingMessage message = CreateMessage();
		message.Write(buffer.Length);
		message.Write(buffer);
		return message;
	}

	/// <summary>
	/// Unpacks object from message using .NET serialization
	/// </summary>
	protected object UnpackObject(NetIncomingMessage message)
	{
		byte[] buffer = message.ReadBytes(message.ReadInt32());
		MemoryStream stream = new MemoryStream(buffer);
		BinaryFormatter formatter = new BinaryFormatter();
		return formatter.Deserialize(stream);
	}

	#endregion

	#region public

	public AutoPeer(NetPeerConfiguration config) : base(config)
	{
		m_messageHandlers = new Dictionary<NetIncomingMessageType, MessageHandler>();
		m_dataHandlers = new Dictionary<Type, DataHandler>();
		DefaultHandlers();
	}

	/// <summary>
	/// Remove all handlers for messages and data
	/// </summary>
	public void ClearHandlers()
	{
		m_messageHandlers.Clear();
		m_dataHandlers.Clear();
	}

	/// <summary>
	/// Remove all handlers and assign default ones (which do only logging)
	/// </summary>
	public virtual void DefaultHandlers()
	{
		ClearHandlers();

		AddMessageHandler(NetIncomingMessageType.Data, DataDispatcher);
		defaultMessageHandler = (message, instance) =>
		{
			Debug.Log(string.Format("Unhandled message type: {0} \nTime: {1} \nSender: {2} \nSize: {3}", message.MessageType, message.ReceiveTime, message.SenderEndPoint, message.LengthBytes));
		};
		defaultDataHandler = (message, data, instance) =>
		{
			Debug.Log(string.Format("Unhandled data type: {0} \nTime: {1} \nSender: {2} \nSize: {3}", data.GetType(), message.ReceiveTime, message.SenderEndPoint, message.LengthBytes));
		};
	}

	/// <summary>
	/// Call this on every Update(), this will dispatch (and possibly unpack) messages to methods that are signed for them
	/// </summary>
	public void DispatchMessages()
	{
		if (Status != NetPeerStatus.Running)
		{
			Debug.LogWarning("Tried to dispatch messages before starting!");
			return;
		}

		NetIncomingMessage message;
		while ((message = ReadMessage()) != null)
		{
			MessageHandler handler;
			if (m_messageHandlers.TryGetValue(message.MessageType, out handler))
			{
				handler(message, this);
			}
			else
			{
				defaultMessageHandler(message, this);
			}
			Recycle(message);
		}
	}

	/// <summary>
	/// Pack an object and send it to the receiver
	/// </summary>
	public void SendObject(object obj, NetConnection receiver, NetDeliveryMethod method)
	{
		SendMessage(PackObject(obj), receiver, method);
	}

	/// <summary>
	/// Pack an object and send it to numerous receivers
	/// </summary>
	public void SendObject(object obj, List<NetConnection> receivers, NetDeliveryMethod method)
	{
		SendMessage(PackObject(obj), receivers, method, 0);
	}

	#region messageHandling

	public delegate void MessageHandler(NetIncomingMessage message,AutoPeer instance);

	public MessageHandler defaultMessageHandler;

	/// <summary>
	/// Overrides currently present message handler, if any, if not - simply adds this message handler
	/// </summary>
	public void OverrideMessageHandler(NetIncomingMessageType type, MessageHandler handler)
	{
		RemoveMessageHandler(type);
		AddMessageHandler(type, handler);
	}

	/// <summary>
	/// Adds to currently present message handlers, if any
	/// </summary>
	public void AddMessageHandler(NetIncomingMessageType type, MessageHandler handler)
	{
		MessageHandler currentHandler;
		if (m_messageHandlers.TryGetValue(type, out currentHandler))
		{
			currentHandler += handler;
		}
		else
		{
			m_messageHandlers.Add(type, handler);
		}
	}

	/// <summary>
	/// Remove previously added message handler
	/// </summary>
	public void RemoveMessageHandler(NetIncomingMessageType type, MessageHandler handler)
	{
		MessageHandler currentHandler;
		if (m_messageHandlers.TryGetValue(type, out currentHandler))
		{
			currentHandler -= handler;
		}
	}

	/// <summary>
	/// Remove all handlers for specifyed message type
	/// </summary>
	public void RemoveMessageHandler(NetIncomingMessageType type)
	{
		m_messageHandlers.Remove(type);
	}

	#endregion

	#region dataHandling

	public delegate void DataHandler(NetIncomingMessage message,object data,AutoPeer instance);

	public DataHandler defaultDataHandler;

	/// <summary>
	/// Overrides currently present data handler, if any, if not - simply adds this data handler
	/// </summary>
	public void OverrideDataHandler(Type type, DataHandler handler)
	{
		RemoveDataHandler(type);
		AddDataHandler(type, handler);
	}

	/// <summary>
	/// Adds to currently present data handlers, if any
	/// </summary>
	public void AddDataHandler(Type type, DataHandler handler)
	{
		DataHandler currentHandler;
		if (m_dataHandlers.TryGetValue(type, out currentHandler))
		{
			currentHandler += handler;
		}
		else
		{
			m_dataHandlers.Add(type, handler);
		}
	}

	/// <summary>
	/// Remove previously added data handler
	/// </summary>
	public void RemoveDataHandler(Type type, DataHandler handler)
	{
		DataHandler currentHandler;
		if (m_dataHandlers.TryGetValue(type, out currentHandler))
		{
			currentHandler -= handler;
		}
	}

	/// <summary>
	/// Remove all handlers for specifyed data type
	/// </summary>
	public void RemoveDataHandler(Type type)
	{
		m_dataHandlers.Remove(type);
	}

	#endregion

	#endregion

}